package com.example.study.jetpack.compose

import android.os.Bundle
import android.util.Log
import androidx.activity.OnBackPressedCallback
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import kotlinx.coroutines.launch


/**
 *  created by 20300
 *   on 2022/3/14
 *   describe:https://blog.csdn.net/Mr_Tony/article/details/118941147?spm=1001.2014.3001.5502
 */
class Compose的附加效应 {
    /*用一句话概括副作用,一个函数的执行过程中,除了返回函数值之外,对调用方还会带来其他附加影响,
    例如修改全局变量或者修改参数等

    LaunchedEffect
    LaunchedEffect意思为在某个可组合项的作用域内运行挂起函数.官方示例无法直接运行,修改后如下
    (这里使用mutableStateOf而不用其他的兼容库)
    */
}

class ComposeEffectActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val mviViewModel = MviViewModel()
        setContent {
            MyScreen(mviViewModel)
        }
    }

    @Composable
    fun MyScreen(
        state: MviViewModel,
        scaffoldState: ScaffoldState = rememberScaffoldState()
    ) {
        val uiState by remember { state.uiState }
        // If the UI state contains an error, show snackbar
        if (uiState.hasError) {

            // `LaunchedEffect` will cancel and re-launch if
            // `scaffoldState.snackbarHostState` changes
            LaunchedEffect(scaffoldState.snackbarHostState) {
                // Show snackbar using a coroutine, when the coroutine is cancelled the
                // snackbar will automatically dismiss. This coroutine will cancel whenever
                // `state.hasError` is false, and only start when `state.hasError` is true
                // (due to the above if-check), or if `scaffoldState.snackbarHostState` changes.
                scaffoldState.snackbarHostState.showSnackbar(
                    message = "Error message",
                    actionLabel = "Retry message"
                )
            }
        }

        Scaffold(scaffoldState = scaffoldState) {
            /* ... */
             Button(onClick = {
                state.onHashErrorChange(true)
            }){
                Text("点我")
            }
        }
    }
}


class MviViewModel{
    val uiState = mutableStateOf(UiState())
    fun onHashErrorChange(hasError: Boolean) {
        uiState.value = uiState.value.copy(hasError = hasError)
        Log.e("YM","--->${uiState}")
    }
}

data class UiState(
    val hasError: Boolean = false
)

/*rememberCoroutineScope
LaunchedEffect是一个可组合函数,所以只能在可组合函数内使用.假如需要在可组合函数外启动协程,
在可组合函数声明周期结束时候取消,可以使用rememberCoroutineScope.另外rememberCoroutineScope
还可以控制多个协程.
示例:点击Button显示一个SnackBar
*/
@Composable
fun MoviesScreen(scaffoldState: ScaffoldState = rememberScaffoldState()) {

    // Creates a CoroutineScope bound to the MoviesScreen's lifecycle
    val scope = rememberCoroutineScope()

    Scaffold(scaffoldState = scaffoldState) {
        Column {
            /* ... */
            Button(
                onClick = {
                    // Create a new coroutine in the event handler
                    // to show a snackbar
                    scope.launch {
                        scaffoldState.snackbarHostState
                            .showSnackbar("Something happened!")
                    }
                }
            ) {
                Text("Press me")
            }
        }
    }
}

/*
rememberUpDatedState
作者不明白函数意思,无记录
主要是使值变化的话不会导致组合重组
*/

/*
DisposableEffect
有时候需要在组合函数变化或者退出时候做些处理,可以使用DisposableEffect
例如,需要注册OnBackPressedCallback才能监听在OnBackPressedDispatcher上按下的返回按钮.
如需在Compose中监听这些事件,请根据需要使用DisposableEffect注册和取消注册其它回调.
*/
/*override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val mviViewModel = MviViewModel()
    setContent {
        BackHandler(onBackPressedDispatcher){//该代码是在Activity中使用
            Log.e("YM","LandingScreen事件已过")
        }
    }

}*/
@Composable
fun BackHandler(backDispatcher: OnBackPressedDispatcher, onBack: () -> Unit) {

    // Safely update the current `onBack` lambda when a new one is provided
    val currentOnBack by rememberUpdatedState(onBack)

    // Remember in Composition a back callback that calls the `onBack` lambda
    val backCallback = remember {
        // Always intercept back events. See the SideEffect for
        // a more complete version
        object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                currentOnBack()
            }
        }
    }

    // If `backDispatcher` changes, dispose and reset the effect
    DisposableEffect(backDispatcher) {
        // Add callback to the backDispatcher
        backDispatcher.addCallback(backCallback)

        // When the effect leaves the Composition, remove the callback
        onDispose {//组合被移除时会调用这个函数
            backCallback.remove()
        }
    }
}

/*
SideEffect
如需与非Compose管理的对象共享Compose状态,请使用SideEffect可组合项,应为每次成功重组时都会调用该
可组合项
*/
@Composable
fun BackHandler(
    backDispatcher: OnBackPressedDispatcher,
    enabled: Boolean = true, // Whether back events should be intercepted or not
    onBack: () -> Unit
) {
    /* ... */
    val backCallback = remember { /* ... */ }

    // On every successful composition, update the callback with the `enabled` value
    // to tell `backCallback` whether back events should be intercepted or not
    SideEffect {
        /*backCallback.isEnabled = enabled*/
    }

    /* Rest of the code */
}

/*
produceState
ProduceState是将非Compose状态转换为Compose状态
这个比较有用,比如有些地方需要一些state值来对UI进行重组,但是这些状态的来源并没有生产状态,可以使用这个转换
示例:
@Composable
fun loadNetworkImage(
    url: String,
    imageRepository: ImageRepository
): State<Result<Image>> {

    // Creates a State<T> with Result.Loading as initial value
    // If either `url` or `imageRepository` changes, the running producer
    // will cancel and will be re-launched with the new keys.
    return produceState(initialValue = Result.Loading, url, imageRepository) {

        // In a coroutine, can make suspend calls
        val image = imageRepository.load(url)

        // Update State with either an Error or Success result.
        // This will trigger a recomposition where this State is read
        value = if (image == null) {
            Result.Error
        } else {
            Result.Success(image)
        }
    }
}
*/

/*
derivedStateOf
derivedStateOf表示将一个或多个状态对象转换为其他状态
如果某个状态是从其他状态对象计算或派生得出的,请用derivedStateOf.使用此函数可确保仅当计算中使用的状态之一
发生变化时才会进行计算
这个函数主要可以理解为多个值合并成State.不管这个值是否是State.另外当其返回的值或者所引用的State发生改变时都会
引发重组,如下示例:
*/
@Composable
fun TodoList(
    highPriorityKeywords: List<String> = listOf("Review", "Unblock", "Compose")
) {
    val todoTasks = remember { mutableStateListOf<String>() }

    // Calculate high priority tasks only when the todoTasks or
    // highPriorityKeywords change, not on every recomposition
    val highPriorityTasks by remember(todoTasks, highPriorityKeywords) {
        derivedStateOf {
            todoTasks.filter {
                it.contains(highPriorityKeywords[0],true)
                //it.containsWord(highPriorityKeywords) ???
            }
        }
    }

    Box(Modifier.fillMaxSize()) {
        LazyColumn {
            items(highPriorityTasks) { /* ... */ }
            items(todoTasks) { /* ... */ }
        }
        /* Rest of the UI where users can add elements to the list */
    }
}

/*
snapshotFlow
将Compose的State转换为Flow
使用snapshotFlow将State对象转换为冷Flow.snapshotFlow会收集到块时运行该块,并发出从块中读取的State对象的
结果.当在snapshotFlow块中读取State对象之一发生变化时,如果新值与之前发出的值不相等,Flow会向其收集器发出新值.
(类似于Flow.distinctUntilChanged的行为)
实例:
val listState = rememberLazyListState()

LazyColumn(state = listState) {
    // ...
}

LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .map { index -> index > 0 }
        .distinctUntilChanged()
        .filter { it == true }
        .collect {
            MyAnalyticsService.sendScrolledPastFirstItemEvent()
        }
}
*/

/*
重启效应
Compose中有一些效应(如LaunchedEffect,ProduceState或DisposableEffect)会采用可变数量的参数和键
来取消运行效应,并使用新的键启动一个新的效应
这些API的典型形式是:
EffectName(restartIfThisKeyChanges, orThisKey, orThisKey, ...) { block }
由于此行为的细微差别,如果用于重启效应的参数不是适当的参数,可能出现问题
`如果重启效应次数不够,可能会导致应用出现错误
`如果重启效应次数过多,效率可能不高
一般来说,效应代码块中使用的可变和不可边变量作为参数添加到效应可组合项中.除此之外,你还可添加更多参数,
以便强制重启效应.如果更改变量不应导致效应重启,则应将该变量封装在rememberUpdatedState中.
如果由于变量封装在一个不含键的remember中使之没有发生变化,则无需将变量作为键传递给效应

在上面显示的DisposableEffect代码中,效应将其块中使用backDispatcher作为参数,因为对它们的任何更改都会导致效应
重启.
@Composable
fun BackHandler(backDispatcher: OnBackPressedDispatcher, onBack: () -> Unit) {
    /* ... */
    val backCallback = remember { /* ... */ }

    DisposableEffect(backDispatcher) {
        backDispatcher.addCallback(backCallback)
        onDispose {
            backCallback.remove()
        }
    }
}
无需使用backCallback作为DisposableEffect键,因为他的值在组合中绝不会发生变化;它封装在不含键的remember中.
如果未将backDispatcher作为参数传递,并且该代码发生变化,那么BackHandler将重组,但DisposableEffect不会进行处理和重启.
这将导致问题,因为伺候会使用错误的backDispatcher
 */

/*
使用常量作为键
可以使用true等常量作为效应键,使其遵循用点的生命周期.它实际上具有有效的用例,如上所示的LaunchedEffect示例.
慎重考虑,确定确实需要这么做
 */




